package com.stylefeng.guns.modular.prediction.service.impl;

import com.github.abel533.echarts.Option;
import com.github.abel533.echarts.axis.CategoryAxis;
import com.github.abel533.echarts.axis.ValueAxis;
import com.github.abel533.echarts.code.Magic;
import com.github.abel533.echarts.code.Tool;
import com.github.abel533.echarts.code.Trigger;
import com.github.abel533.echarts.code.X;
import com.github.abel533.echarts.feature.MagicType;
import com.github.abel533.echarts.json.GsonOption;
import com.github.abel533.echarts.series.Line;
import com.google.common.collect.Lists;
import com.stylefeng.guns.common.constant.factory.ConstantFactory;
import com.stylefeng.guns.common.exception.BussinessException;
import com.stylefeng.guns.common.persistence.model.Dict;
import com.stylefeng.guns.common.plugin.entity.Period;
import com.stylefeng.guns.core.support.DateTimeKit;
import com.stylefeng.guns.modular.prediction.util.MultiFactorForecasting;
import com.stylefeng.guns.modular.yjya.service.IAccidentHistoryService;
import com.vip.vjtools.vjkit.time.DateUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;

import static com.stylefeng.guns.common.exception.BizExceptionEnum.DATA_LOSE;
import static java.util.stream.Collectors.toList;


@Service
public class PredictionServiceImpl {


	@Autowired
	private IAccidentHistoryService accidentHistoryService;


	public Option calculateByYear(String category, Date year) {
		List<Period> yearList = beforeYearList(year, 4);
		return calculate(category, year, "yyyy", yearList);
	}


	public Option calculateByMonth(String category, Date month) {
		List<Period> monthList = beforeMonthList(month, 4);
		return calculate(category, month, "yyyy-MM", monthList);
	}



	private Option calculate(String category,Date forcastDate, String pa, List<Period> beforePeriodList){
		List<List<Integer>> numOfCauseTheDateListList = numOfCauseTheDateListList(category, beforePeriodList);
		List<Integer> accidentList = accidentNumList(category, beforePeriodList);
		if (!dataIsEffective(accidentList,numOfCauseTheDateListList)) {
			throw new BussinessException(DATA_LOSE);
		}
		List<Double>  forecastList = MultiFactorForecasting.forecast(accidentList, numOfCauseTheDateListList,1);
		List<String> yList = beforePeriodList.stream().map(period -> period.getBeginDate()).map(date -> DateTimeKit.format(date,pa)).collect(toList());
		yList.add(DateTimeKit.format(forcastDate, pa));
		return toOption(accidentList,forecastList,yList);
	}



	private boolean dataIsEffective(List<Integer> accidentList,List<List<Integer>> numForCauseOfTheDateListList){

		for (int i = 0; i < accidentList.size(); i++) {
			if (accidentList.get(i) == 0) {
				for (List<Integer> numForCauseOfTheDateList : numForCauseOfTheDateListList) {
					Integer numForCauseOfTheDate = numForCauseOfTheDateList.get(i);
					if (numForCauseOfTheDate > 0) {
						return true;
					}
				}
				return false;
			}
		}

		return true;
	}

	private Option toOption(List<Integer> actualList, List<Double> forecastList,List<String> yList) {
		Option option = new GsonOption();
		option.title().text("多因素预测").x(X.center);


		option.legend().x(X.left).data("实际", "预测");

		option.toolbox().show(false)
				.feature(Tool.mark, Tool.dataView, new MagicType(Magic.line, Magic.bar), Tool.restore,
						Tool.saveAsImage);
		option.tooltip().trigger(Trigger.item).formatter("{a} <br/>{b} : {c}");
		option.calculable(true);

		option.yAxis(new ValueAxis().name("事故次数").interval(100));

		CategoryAxis categoryAxis = new CategoryAxis();
		categoryAxis.name("年份").splitLine().show(false);
		categoryAxis.setData(yList);

		option.xAxis(categoryAxis);

		ValueAxis valueAxis = new ValueAxis();
		valueAxis.axisLabel().formatter("{value}");
		option.xAxis(valueAxis);

		Line line = new Line("实际");
		line.setData(actualList);
		Line line2 = new Line("预测");
		line2.setData(forecastList);
		option.series(line, line2);
		return option;
	}

	public List<Period> beforeMonthList(Date month,int before){
		List<Period> beforeMonthList = Lists.newArrayListWithCapacity(before);

		Date fistMonthBegin = month;
		for(int i = 0 ; i< before ; i++){
			fistMonthBegin = DateUtil.beginOfMonth(DateUtil.subHours(fistMonthBegin, 1));
		}

		Date monthBegin = fistMonthBegin;
		for(int i = 0; i< before ; i++){
			Period period = new Period(monthBegin,DateUtil.endOfMonth(monthBegin));
			beforeMonthList.add(period);
			monthBegin = DateUtil.nextMonth(monthBegin);
		}
		return beforeMonthList;
	}



	public List<Period> beforeYearList(Date year, int before) {

		List<Period> beforeYearList = new ArrayList<>();

		for (int i = 0; i < before; i++) {
			beforeYearList.add(null);
		}

		Date nextYearBegin = year;
		for (int i = 1; i <= before; i++) {
			Date end = new Date(nextYearBegin.getTime() - i);
			Date begin = DateUtil.beginOfYear(end);
			Period period = new Period(begin,end);
			beforeYearList.set(before - i, period);

			nextYearBegin = begin;
		}

		return beforeYearList;
	}

	public List<List<Integer>> numOfCauseTheDateListList(String category, List<Period> dateList) {
		List<String> causeList = ConstantFactory.me().
				getDictByCode("causeCode").stream().map(Dict::getCode).collect(toList());
		return causeList.stream()
						.map(cause -> numOfCauseTheDateList(category,cause,dateList))
						.collect(toList());
	}

	private List<Integer> numOfCauseTheDateList(String category, String cause, List<Period> dateList) {
		return dateList.stream()
						.map(period ->
								accidentHistoryService.selectFactorCountByCategoryCauseAndDate(category,cause,period.getBeginDate(),period.getEndDate()))
						.collect(toList());
	}


	public List<Integer> accidentNumList(String catetory, List<Period> dateList) {
		List<Integer> list = new ArrayList<>();
		for (Period period : dateList) {
			int accidentCount = accidentHistoryService
					.selectAccidentCountByCategoryAndDate(catetory, period.getBeginDate(), period.getEndDate());
			list.add(accidentCount);
		}
		return list;
	}

}
